﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace LowLevelGraphics.Filter
{
    // sometimes we get an empty pic. (with special pics, jpgs e.g.)
    /// <summary>
    /// This should work now for most cases,
    /// </summary>
    public class EdgeEnergyMap : BaseImageFilter 
    {
        /// <summary>
        /// default constructor
        /// </summary>
        public EdgeEnergyMap()
        {
        }

        /// <summary>
        /// executes this filter
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            _bitmap = CalculateEnergyMatrix(_bitmap);
            return _bitmap;
        }

        /// <summary>
        /// Calculates the energy matrix.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <returns></returns>
        public UnsafeBitmap CalculateEnergyMatrix(UnsafeBitmap _bitmap)
        {
            BitmapData bmData = _bitmap.BitmapData; //image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            //address of the first line. IntPtr is a platform specific type that represents pointer in C#.
            IntPtr ptr = bmData.Scan0;
            //one line length of an image in rgb pixel including padding.
            int stride = bmData.Stride;

            //if you notice, here we use 2 image. One image from method parameter and one to be an output image.
            //You can guess that at first the output image (newBm) is empty. 
            UnsafeBitmap newBm = new Bitmap(_bitmap.Width, _bitmap.Height);
            BitmapData newBmData = newBm.BitmapData; //newBm.LockBits(new Rectangle(0, 0, newBm.Width, newBm.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            IntPtr newPtr = newBmData.Scan0;
            int newStride = newBmData.Stride;

            unsafe
            {
                int nBytesPerPixelCount = _bitmap.BytesPerPixelCount;
                //convert pointer ptr into byte* explicitly.
                byte* p = (byte*)ptr;
                int rgbWidth = _bitmap.Width * nBytesPerPixelCount; //_bitmap.Width * 3;
                int paddingOffset = stride - rgbWidth;

                byte* newP = (byte*)newPtr;
                int newRgbWidth = newBm.Width * nBytesPerPixelCount; //newBm.Width * 3;
                int newPaddingOffset = newStride - newRgbWidth;

                int rLrR = 0, gLgR = 0, bLbR = 0, rTrB = 0, gTgB = 0, bTbB = 0;
                byte result;
                double dIdx = 0, dIdy = 0;

                int nHeight = _bitmap.Height;
                int nWidth = _bitmap.Width;
                int j = 0;
                int i = 0;
                int offSetL = 0;
                int offSetR = 0;
                int offSetT = 0;
                int offSetB = 0;

                for (j = 0; j < nHeight; j++)
                {
                    for (i = 0; i < nWidth; i++)
                    {
                        //at the topmost row we don't have p[-3] => B ,p[-2] => G, p[-1] => R. 
                        //So we use p[0],p[1],p[2] to replace that respectively.
                        offSetT = 0;
                        offSetB = 0;
                        if (j == 0) offSetT = stride; else if (j == nHeight - 1) offSetB = -stride;

                        bLbR = p[-stride + offSetT] - p[stride + offSetB];
                        gTgB = p[-stride + 1 + offSetT] - p[stride + 1 + offSetB];
                        rLrR = p[-stride + 2 + offSetT] - p[stride + 2 + offSetB];

                        //handle leftmost and rightmost column.
                        offSetL = 0;
                        offSetR = 0;
                        if (i == 0) offSetL = nBytesPerPixelCount; else if (i == nWidth - 1) offSetR = -nBytesPerPixelCount;
                        //if (i == 0) offSetL = 3; else if (i == nWidth - 1) offSetR = -3;

                        //rLrR is Bleft - Bright, the same for the others.
                        bLbR = p[-4 + offSetL] - p[4 + offSetR];
                        gLgR = p[-3 + offSetL] - p[5 + offSetR];
                        rLrR = p[-2 + offSetL] - p[6 + offSetR];

                        dIdx = PythagoreanAdd(bLbR, gLgR, rLrR);
                        dIdy = PythagoreanAdd(bTbB, gTgB, rTrB);

                        //get the energy result from computing R,G,B channel vectors.
                        result = (byte)Math.Sqrt(dIdx + dIdy);

                        //save the energy value to our new locked image (newBm) via its pointer (newP).
                        newP[0] = result;
                        newP[1] = result;
                        newP[2] = result;

                        //advance the pointers by 3 (R,G,B).
                        p += nBytesPerPixelCount; //p += 3;
                        newP += nBytesPerPixelCount;//newP += 3;
                    }

                    //add paddingOffset to skip to the next row.
                    p += paddingOffset;
                    newP += newPaddingOffset;
                }
            }
            //release the resources.

            BitmapFunctions.Copy(newBm, _bitmap);
            newBm.UnlockImage();
            return _bitmap;
        }      

        /// <summary>
        /// Pythagorean addition
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="c"></param>
        /// <returns></returns>
        private double PythagoreanAdd(int a, int b, int c)
        {
            return a * a + b * b + c * c;
        }

        /// <summary>
        /// Used to create a deep copy
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new EdgeEnergyMap();
        }
    }
}
